/*  

Cafechess Library: Cover basic chess programming needs, like GUI, Engine integration, 
FEN and PGN parsing.

                 \\|//
                 (o o)
--------------ooO-(_)-Ooo----------------------------------
Copyright (C) 2009  By Gregory A. Prentice Snoqualmie, WA
                    www.cafechess.org
                    cafechess@comcast.net
                      Oooo
--------------oooO----(  )---------------------------------
              (  )    (_/
               \_)

Microsoft Public License (Ms-PL)
               
Revisions:

Author        Date    Description:

*/
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;

using System.Windows.Forms;

using Cafechess.Chess.Validation;
using Cafechess.General.Handle;
using Cafechess.Chess.General;
using Cafechess.Chess.Controls;

namespace Cafechess.Chess.Controls
{
	/// <summary>
	/// A windows form control used to display a chessboard,
	/// provide the ability to move pieces, and add graphical
	/// annotations to the boards such as arrows, boxes, and circles.
	/// Used for input to / from a chess engine for manipulation of
	/// the chess pieces.  
  /// By pressing the "Alt" key plus one of the following:
  /// <list type="bullet">
  /// <item>F9  - turns on the attack square highlighter for any major piece that is moved.</item>
  /// <item>F10 - turns on the square highlighter for major piece move pattern.</item>
  /// <item>F11 - turns on the square highlighter for major piece move pattern.</item>
  /// </list>
  /// The basic steps for setting up a control
	/// after you've add the control to your form is as follows:
	/// <code>
  ///       NewChessboard.ChessBoardGui.Validation = new StandardValidation();
  ///       NewChessboard.ChessBoardGui.Validation.AddEvents(this);      
  ///       IChessboardGui igui = NewChessboard.ChessBoardGui;          
  ///       igui.ActiveLayer = cafechess.control.chessboard.Layers.DrawTop;      
  /// </code>
	/// </summary>  
  public partial class ChessBoard : System.Windows.Forms.UserControl
	{
    /// <summary>
    /// Represents the drawing surface.
    /// </summary>
    ChessSurface coChessSurface;
    /// <summary>
    /// Handler for any mouse events we want to capture.
    /// </summary>
    IMouseEvents coMouseEvents;
    /// <summary>
    /// Handler for our drag and drop opperations, moving a piece.
    /// </summary>
    IDragDropEvents coDragEvents;
    /// <summary>
    /// Our default cursor.
    /// </summary>
    Cursor coOpenHand;
    /// <summary>
    /// Used when displaying alternating attacks.
    /// </summary>
    bool coToggleAttacks = true;    
    /// <summary>
    /// Represents the last Alt key combinations pressed for our attacks.
    /// </summary>
    Keys coLastKey=Keys.None;

    bool coColorAttacks;
    /// <summary>
    /// Used to allow us to quickly reset the highlighted squares.
    /// </summary>
    readonly BitArray coEmptyArray = new BitArray(64, false);

    /// <summary>
    /// Are we capturing the alt key combinations.
    /// </summary>
    public bool EnableAltKeys
    {
      set;
      get;
    }

    /// <summary>
    /// Used as the default constructor for our chessboard control
    /// </summary>
		public ChessBoard()
		{
      // This call is required by the Windows.Forms Form Designer.
      InitializeComponent();
      SetStyle(ControlStyles.DoubleBuffer, true);
      SetStyle(ControlStyles.UserPaint, true);
      SetStyle(ControlStyles.AllPaintingInWmPaint, true);
      this.EnableAltKeys = true;
      coChessSurface = new ChessSurface(this.ClientRectangle, this.BackColor);
      coChessSurface.BoarderWidth = 15;
      coChessSurface.Control = this;
      PieceLayer pieces = coChessSurface.PieceLayer;
      coDragEvents = pieces;
      pieces.ChessImages = imagePieces;
      coMouseEvents = pieces;      
		}

    /// <summary>
    /// Sets our default cursor to an open hand.
    /// </summary>
    override protected Cursor DefaultCursor
    {
      get 
      {
        // Note we use to new up the coOpenHand in the contructor, but gave the VS designer
        //  a fit.  So we new it up if null here in the DefaultCursor.
        if (coOpenHand == null)
        {
          coOpenHand = new Cursor(GetType(), "Cursors.OpenHand.cur");
        }
        return coOpenHand;
      }
    }
    
    /// <summary>
    /// The main interface for interaction with the chess GUI.  Gives
    /// you access to the many drawing features of the board, validation,
    /// and FEN / DFEN manipulation.
    /// </summary>
    public IChessboardGui ChessBoardGui
    {
      get{ return coChessSurface; }
    }

    private void ChessBoard_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    { 
      // Set to same as our surface InterpolationMode
      e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
      coChessSurface.Render(e.Graphics);    
    }

    private void ChessBoard_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {      
      if( coMouseEvents != null )
        coMouseEvents.DownEvent(sender, e);
    }

    private void ChessBoard_MouseEnter(object sender, System.EventArgs e)
    {
      if( coMouseEvents != null )
        coMouseEvents.EnterEvent(sender, e);   
    }

    private void ChessBoard_MouseHover(object sender, System.EventArgs e)
    {
      if( coMouseEvents != null )
        coMouseEvents.HoverEvent(sender, e);   
    }

    private void ChessBoard_MouseLeave(object sender, System.EventArgs e)
    {
      if( coMouseEvents != null )
        coMouseEvents.LeaveEvent(sender, e);   
    }

    private void ChessBoard_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
      if( coMouseEvents != null )
        coMouseEvents.MoveEvent(sender, e);
    }

    private void ChessBoard_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
    {
      if( coMouseEvents != null )
        coMouseEvents.UpEvent(sender, e);
      if( coToggleAttacks )
      {
        if( coLastKey == Keys.F9 )
          HighlightPiece(true);
        else if( coLastKey == Keys.F10 )
          HighlightAttacks(true);
        else if(coLastKey == Keys.F11 )
        {
          coColorAttacks = !coChessSurface.Validation.SideToMove;
          HighlightAttacks(true);
        }
        else if (coLastKey == Keys.F12)
        {
          coColorAttacks = !coChessSurface.Validation.SideToMove;
          HighlightWeakSquares(true);
        }

      }

    }

    private void ChessBoard_Resize(object sender, System.EventArgs e)
    {
      if(! ClientRectangle.IsEmpty)
      {
        coChessSurface.Square = ClientRectangle;
        Invalidate();
      }
    }

    private void ChessBoard_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
    {
      if(coDragEvents != null )
        coDragEvents.DragDropEvent(sender,e);    
    }

    private void ChessBoard_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
    {      
      if(coDragEvents != null )
        coDragEvents.DragEnterEvent(sender,e);
    }

    private void ChessBoard_DragLeave(object sender, System.EventArgs e)
    {
      if(coDragEvents != null )
        coDragEvents.DragLeaveEvent(sender,e);
    }

    private void ChessBoard_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
    {
      if(coDragEvents != null )
        coDragEvents.DragOverEvent(sender,e);
    }

    private void ChessBoard_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
    {
      SetKeyEvents(e,true);
    }

    private void ChessBoard_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
    {
      SetKeyEvents(e,false);
    }
    private void ChessBoard_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
    {
      e.Handled = false;
    }

    /// <summary>
    /// Common routine used to handle the alt key combinations.
    /// </summary>
    /// <param name="e"></param>
    /// <param name="keyDown"></param>
    void SetKeyEvents(System.Windows.Forms.KeyEventArgs e, bool keyDown)
    {
      // Only if we are trapping the alt key combinations.
      if( this.EnableAltKeys )
      {
        bool handled = false;
        if( coChessSurface.IsDrawing )
        {
          // Only if we are NOT currently dragging an object on our board.
          if( coChessSurface.IsLayerDragging == false )
          {
            if(e.Control && e.Alt )
            {
              coMouseEvents = coChessSurface.MidDrawingLayer; 
              coChessSurface.MidDrawingLayer.CurrentKey = e.KeyCode;       
              handled = true;
            }
            else if(e.Control )
            {
              coMouseEvents = coChessSurface.TopDrawingLayer;
              coChessSurface.TopDrawingLayer.CurrentKey = e.KeyCode;
              handled = true;
            }
            else if(e.Alt)
            {
            }
            else
            {        
              coMouseEvents = coChessSurface.PieceLayer;
            }
          }
        }
        if( ! handled )
        {
          if(e.Alt)
          {
            // Show Attacking squares.
            if( keyDown && (e.KeyCode == Keys.F10 || e.KeyCode == Keys.F11))
            {
              if( coLastKey != e.KeyCode )
                coToggleAttacks = true;
              else
                coToggleAttacks = !coToggleAttacks;

              coColorAttacks = coChessSurface.Validation.SideToMove;
              HighlightAttacks( coToggleAttacks );
              coLastKey = e.KeyCode;
            }
            else if( keyDown && e.KeyCode == Keys.F9)
            {
              if( coLastKey != e.KeyCode )
                coToggleAttacks = true;
              else
                coToggleAttacks = !coToggleAttacks;
              HighlightPiece(coToggleAttacks);
              coLastKey = e.KeyCode;
            }
            else if (keyDown && e.KeyCode == Keys.F12)
            {
              if (coLastKey != e.KeyCode)
                coToggleAttacks = true;
              else
                coToggleAttacks = !coToggleAttacks;
              coColorAttacks = coChessSurface.Validation.SideToMove;
              HighlightWeakSquares(coToggleAttacks);
              coLastKey = e.KeyCode;
            }
          }
        }
      }
    }

    /// <summary>
    /// Highlight a specific pieces complete attack pattern as if no other
    /// pieces were on the board.
    /// </summary>
    /// <param name="on"></param>
    void HighlightPiece(bool on)
    {
      BitArray ba = null;
      Color acolor = on?coChessSurface.AttacksColor:Color.Transparent;
      // Make sure we find a piece.
      DrawablePiece dp = coChessSurface.PieceLayer.SquareClicked;
      if(dp != null)
      {
        switch( dp.Piece ) //!= Cafechess.General.Pieces.None) 
        {
          case Pieces.BlackPawn:
          case Pieces.WhitePawn:
          case Pieces.None:
            ba = coEmptyArray;
            break;
          default:
            if( coChessSurface.Validation != null )
            {
              ba = coChessSurface.Validation.Moves(dp.Piece,BitboardSquare.AlphaToSquare(dp.Name));
            }
            break;
        }
      }
      // Only if we had a piece located on the square clicked.
      if( ba != null )
      {
        coChessSurface.AlternateColorSquares(ba,acolor);
        Invalidate();
      }
    }

    /// <summary>
    /// Highlight the square that are under attack.
    /// </summary>
    /// <param name="on"></param>
    void HighlightAttacks(bool on)
    {
      BitArray ba = null;
      Color acolor = on?coChessSurface.AttacksColor:Color.Transparent;
      ba = coChessSurface.Validation.Attacks(coColorAttacks);
      if( ba != null )
      {
        coChessSurface.AlternateColorSquares(ba,acolor);
        Invalidate();
      }
    }

    void HighlightWeakSquares(bool on)
    {
      // Note on weak squares the only color used here is the Color.Transparent
      //  which sets things back to normal.
      Color acolor = on ? coChessSurface.AttacksColor : Color.Transparent;
      int [] weak = coChessSurface.Validation.WeightWeakSquares();
      BitArray ba = new BitArray(64);
      ulong mask = BitboardSquare.MaskHighBit;
      
      while(mask > 0)
      {
        int ndx = BitboardSquare.MaskToSquare(mask);        
        if (coColorAttacks)
        {
          // White weak squares.
          if (weak[ndx] > 0)
            ba[ndx] = true;
          else
            ba[ndx] = false;
        }
        else
        {
          // Black weak squares.
          if (weak[ndx] < 0)
            ba[ndx] = true;
          else
            ba[ndx] = false;
        }
        mask = mask >> 1;
      }

      if (ba != null)
      {
        coChessSurface.AlternateColorSquares(ba, weak, acolor);
        Invalidate();
      }
    }


	}
}
